package com.resrun.service.pdf;

import com.itextpdf.text.BadElementException;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Image;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfSignatureAppearance;
import com.itextpdf.text.pdf.PdfStamper;
import com.itextpdf.text.pdf.security.*;
import com.resrun.enums.SignTypeEnum;
import com.resrun.exception.CaException;
import com.resrun.exception.SignException;
import com.resrun.model.CACert;
import com.resrun.service.cert.CAService;
import com.resrun.service.pojo.CertificateProperty;
import com.resrun.service.pojo.RealPositionProperty;
import com.resrun.service.pojo.SourcePositionProperty;
import com.resrun.service.verify.SignVerifyService;
import com.resrun.utils.Base64;
import com.resrun.vo.request.PositionRequest;
import com.resrun.vo.request.SealSignRequest;
import com.resrun.vo.request.SignatureSignRequest;
import com.resrun.vo.response.SignResponse;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.OperatorCreationException;
import org.springframework.stereotype.Service;
import org.springframework.util.ResourceUtils;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.List;

/**
 * @Description: 签署业务
 * @Package: com.resrun.service.pdf
 * @ClassName: SignService
 * @copyright 北京资源律动科技有限公司
 */
@Slf4j
@Service
@AllArgsConstructor
public class SignService {

    private CAService caService;
    private SignVerifyService signVerifyService;
    private CalculatePositionService calculatePositionService;

    // 个人签名图片大小
    private static final int PERSONAL_SIGNATURE_WIDTH = 150;
    private static final int PERSONAL_SIGNATURE_HEIGHT = 70;
    // 企业印章图片大小
    private static final int ENTERPRISE_SEAL_WIDTH = 200;
    private static final int ENTERPRISE_SEAL_HEIGHT = 200;

    // 是否判断校验不校验PDF页码
    private static final boolean INSPECT = true;
    private static final int CERTIFICATION_LEVEL = PdfSignatureAppearance.NOT_CERTIFIED;
    private static final PdfSignatureAppearance.RenderingMode RENDERING_MODE = PdfSignatureAppearance.RenderingMode.GRAPHIC;
    private static final String HASH_ALGORITHM = DigestAlgorithms.SHA256;
    private static final MakeSignature.CryptoStandard CRYPTO_STANDARD = MakeSignature.CryptoStandard.CMS;

    // 原因
    private static final String REASON = "防伪造防篡改数字校验";


    public SignResponse signPersonal(SignatureSignRequest request) {
        byte[] signFileBytes = getSignFileBytes("开源工具版说明.pdf");
        if (ArrayUtils.isEmpty(signFileBytes)) {
            throw new SignException("解析文件异常");
        }

        List<RealPositionProperty> positionList = getSignaturePosition(request);
        if (CollectionUtils.isEmpty(positionList)) {
            throw new SignException("签名位置为空");
        }

        byte[] signatureBytes = Base64.decode(request.getSignature());
        if (ArrayUtils.isEmpty(signatureBytes)) {
            throw new SignException("签名信息为空");
        }

        CertificateProperty certProperty = loadCertOfPersonal(request.getUserId());
        if (certProperty == null) {
            throw new CaException("获取证书异常");
        }

        byte[] resultBytes = signContract(signFileBytes, positionList, signatureBytes, certProperty);
        if (ArrayUtils.isEmpty(resultBytes)) {
            throw new SignException("文件签署异常");
        }

        String encode = Base64.encode(resultBytes);
        SignResponse response = new SignResponse();
        response.setSignFile(encode);

        return response;
    }

    public SignResponse signEnterprise(SealSignRequest request) {
        byte[] signFileBytes = getSignFileBytes("开源工具版说明.pdf");
        if (ArrayUtils.isEmpty(signFileBytes)) {
            throw new SignException("解析文件异常");
        }

        List<RealPositionProperty> positionList = getSealPosition(request);
        if (CollectionUtils.isEmpty(positionList)) {
            throw new SignException("签名位置为空");
        }

        byte[] sealBytes = Base64.decode(request.getSeal());
        if (ArrayUtils.isEmpty(sealBytes)) {
            throw new SignException("印章信息为空");
        }

        CertificateProperty certProperty = loadCertOfEnterprise(request.getEnterpriseId());
        if (certProperty == null) {
            throw new CaException("获取证书异常");
        }

        byte[] resultBytes = signContract(signFileBytes, positionList, sealBytes, certProperty);
        if (ArrayUtils.isEmpty(resultBytes)) {
            throw new SignException("文件签署异常");
        }

        String encode = Base64.encode(resultBytes);
        SignResponse response = new SignResponse();
        response.setSignFile(encode);

        return response;
    }

    public List<RealPositionProperty> getSignaturePosition(SignatureSignRequest request) {
        if (SignTypeEnum.POSITION.getCode().equals(request.getSignType())) {
            if (CollectionUtils.isEmpty(request.getPositionList())) {
                throw new CaException("获取印章签署位置异常");
            }

            //计算个人签署位置
            List<SourcePositionProperty> convert = convert(request.getPositionList());
            byte[] signFileBytes = getSignFileBytes("开源工具版说明.pdf");
            return calculatePositionService.calculatePositions(convert, signFileBytes);
        } else if (SignTypeEnum.KEYWORD.getCode().equals(request.getSignType())) {
            if (StringUtils.isBlank(request.getKeyword())) {
                throw new CaException("获取印章签署位置异常");
            }

            //根据关键字计算所有个人签署位置
            byte[] signFileBytes = getSignFileBytes("开源工具版说明.pdf");
            List<RealPositionProperty> personalPositionList = calculatePositionService.getAllPositionByKeyWords(signFileBytes, request.getKeyword(), PERSONAL_SIGNATURE_WIDTH, PERSONAL_SIGNATURE_HEIGHT);
            if (CollectionUtils.isEmpty(personalPositionList)) {
                throw new CaException("获取印章签署位置异常：签署关键字在文件中不存在");
            }

            return personalPositionList;
        }

        throw new CaException("获取印章签署位置异常：签署类型不正确");
    }

    public List<RealPositionProperty> getSealPosition(SealSignRequest request) {
        if (SignTypeEnum.POSITION.getCode().equals(request.getSignType())) {
            if ((request.getPositionList() == null || request.getPositionList().isEmpty())) {
                throw new CaException("获取印章签署位置异常");
            }

            //计算企业签署位置
            List<SourcePositionProperty> convert = convert(request.getPositionList());
            byte[] signFileBytes = getSignFileBytes("开源工具版说明.pdf");
            return calculatePositionService.calculatePositions(convert, signFileBytes);
        } else if (SignTypeEnum.KEYWORD.getCode().equals(request.getSignType())) {
            if (StringUtils.isBlank(request.getKeyword())) {
                throw new CaException("获取印章签署位置异常");
            }

            //根据关键字计算所有企业签署位置
            byte[] signFileBytes = getSignFileBytes("开源工具版说明.pdf");
            List<RealPositionProperty> entPositionList = calculatePositionService.getAllPositionByKeyWords(signFileBytes, request.getKeyword(), ENTERPRISE_SEAL_WIDTH, ENTERPRISE_SEAL_HEIGHT);
            if (CollectionUtils.isEmpty(entPositionList)) {
                throw new CaException("获取印章签署位置异常：签署关键字在文件中不存在");
            }

            return entPositionList;
        }

        throw new CaException("获取印章签署位置异常：签署类型不正确");
    }

    public CertificateProperty loadCertOfPersonal(final Long userId) {
        if (userId == null) {
            throw new CaException("获取个人证书失败");
        }

        // note：暂时查最新的证书
        CACert cert = caService.getLatestCertByUserId(userId);
        if (cert == null) {
            throw new CaException("请先申请CA证书");
        }

        CertificateProperty certProperty = new CertificateProperty();
        certProperty.setCertType("PKCS12");
        certProperty.setCertBytes(cert.getEncode());
        certProperty.setPassword(cert.getPassword());

        return certProperty;
    }

    public CertificateProperty loadCertOfEnterprise(final Long enterpriseId) {
        if (enterpriseId == null) {
            throw new CaException("获取企业证书失败");
        }

        // note：暂时查最新的证书
        CACert cert = caService.getLatestCertByEnterpriseId(enterpriseId);
        if (cert == null) {
            throw new CaException("请先申请CA证书");
        }

        CertificateProperty certProperty = new CertificateProperty();
        certProperty.setCertType("PKCS12");
        certProperty.setCertBytes(cert.getEncode());
        certProperty.setPassword(cert.getPassword());

        return certProperty;
    }

    /**
     * 签署合同
     *
     * @param signFileBytes 待签名文件字节数组
     * @param positionList  签名位置信息
     * @param badgeBytes    签名标记（签名或印章）字节数组
     * @param certProperty  证书信息
     * @return byte[]
     */
    public byte[] signContract(byte[] signFileBytes, List<RealPositionProperty> positionList, byte[] badgeBytes, CertificateProperty certProperty) {
        if (CollectionUtils.isEmpty(positionList)) {
            return signFileBytes;
        }

        byte[] resultBytes = signFileBytes;
        try {
            // 所有个人位置签署
            for (RealPositionProperty position : positionList) {
                resultBytes = doSign(resultBytes, badgeBytes, certProperty, position);
            }
        } catch (Exception e) {
            log.error("签署异常", e);
        }

        return resultBytes;
    }

    private byte[] getSignFileBytes(String fileName) {
        try {
            byte[] signFileBytes = getResourceFiles(fileName);
            if (ArrayUtils.isEmpty(signFileBytes)) {
                throw new RuntimeException("文件解析失败");
            }

            return signFileBytes;
        } catch (Exception e) {
            log.error("文件解析失败", e);
            throw new RuntimeException("文件解析异常");
        }
    }

    public List<SourcePositionProperty> convert(List<PositionRequest> positionRequestList) {
        List<SourcePositionProperty> list = new ArrayList<>();
        for (PositionRequest request : positionRequestList) {
            SourcePositionProperty position = new SourcePositionProperty();
            position.setOffsetX(Float.valueOf(request.getOffsetX()));
            position.setOffsetY(Float.valueOf(request.getOffsetY()));
            position.setPage(request.getPage());
            position.setWidth(Float.valueOf(request.getWidth()));
            position.setHeight(Float.valueOf(request.getHeight()));
            position.setPageHeight(Float.valueOf(request.getPageHeight()));
            position.setPageWidth(Float.valueOf(request.getPageWidth()));
            list.add(position);
        }
        return list;
    }

    public byte[] getResourceFiles(String path) {
        try {
            try (InputStream inputStream = ResourceUtils.class.getClassLoader().getResourceAsStream(path); ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
                if (inputStream == null) {
                    return new byte[0];
                }

                byte[] buffer = new byte[1024];
                int num = inputStream.read(buffer);
                while (num != -1) {
                    baos.write(buffer, 0, num);
                    num = inputStream.read(buffer);
                }

                baos.flush();
                return baos.toByteArray();
            }
        } catch (Exception e) {
            log.error("获取文件资源异常", e);
            throw new RuntimeException("获取文件失败");
        }
    }

    public byte[] doSign(byte[] pdfFile, byte[] signBadge, CertificateProperty cert, RealPositionProperty position) throws GeneralSecurityException, IOException, DocumentException {
        System.setProperty("javax.xml.parsers.DocumentBuilderFactory", "com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl");
        Security.addProvider(new BouncyCastleProvider());
        // 1、解析证书
        // Java 安全属性文件中指定的默认 keystore 类型；如果不存在此类属性，则返回字符串 "jks"。 PKCS12
        KeyStore ks = KeyStore.getInstance(cert.getCertType());
        char[] chars = cert.getPassword().toCharArray();
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(cert.getCertBytes());
        ks.load(byteArrayInputStream, chars);

        // 获取keystore中的所有别名
        String alias = ks.aliases().nextElement();
        // 返回：请求的密钥，入力参数：别名，用于恢复密钥的密码
        PrivateKey pk = (PrivateKey) ks.getKey(alias, cert.getPassword().toCharArray());
        // 证书链（按用户证书在前，根证书授权在后的顺序）
        Certificate[] chain = ks.getCertificateChain(alias);

        PdfReader reader = null;
        PdfStamper stamper = null;

        try (ByteArrayOutputStream signedFile = new ByteArrayOutputStream()) {
            //2、读取PDF文件
            reader = new PdfReader(pdfFile);
            stamper = PdfStamper.createSignature(reader, signedFile, '\0', null, true);
            //3、给签署属性服务
            PdfSignatureAppearance appearance = getPdfSignatureAppearance(signBadge, position, stamper);
            //4、调用签署  Creating the signature
            ExternalSignature pks = new PrivateKeySignature(pk, HASH_ALGORITHM, BouncyCastleProvider.PROVIDER_NAME);
            ExternalDigest digest = new BouncyCastleDigest();
            MakeSignature.signDetached(appearance, digest, pks, chain, null, null, null, 0, CRYPTO_STANDARD);
            return signedFile.toByteArray();
        } finally {
            // 关闭流
            if (stamper != null) {
                stamper.close();
            }
            if (reader != null) {
                reader.close();
            }
        }
    }

    private PdfSignatureAppearance getPdfSignatureAppearance(byte[] signBadge, RealPositionProperty position, PdfStamper stamper) throws BadElementException, IOException {
        PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
        if (signBadge == null || position == null) {
            appearance.setCertificationLevel(CERTIFICATION_LEVEL);
        } else {
            int pageNum = getPageNum(position);
            appearance.setVisibleSignature(new Rectangle(position.getStartx(), position.getStarty(), position.getEndx(), position.getEndy()), pageNum, null);
            // 添加签章图片
            Image img = Image.getInstance(signBadge);
            appearance.setSignatureGraphic(img);
            appearance.setImageScale(-1);
            appearance.setCertificationLevel(CERTIFICATION_LEVEL);
            appearance.setRenderingMode(RENDERING_MODE);
        }

        appearance.setReason(REASON);
        appearance.setLocation(null);

        return appearance;
    }

    private static int getPageNum(RealPositionProperty position) {
        if (INSPECT) {
            //如果检查就会抛出检查异常
            int pageNum = position.getPageNum();
            if (pageNum == 0) {
                throw new IllegalArgumentException("PDF文件页码必须大于0！");
            }
            return pageNum;
        } else {
            return position.getPageNum() <= 0 ? 1 : position.getPageNum();
        }
    }

}